using System;
using System.Collections.ObjectModel;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Threading.Tasks;
using Beaker.OpenCube.Render.Maths;
using Beaker.OpenCube.Render.Sampling;
using Beaker.OpenCube.Render.Tracing;
using Beaker.Services;
using Beaker.Services.Tasks;

namespace Beaker.OpenCube.Render.Cameras
{
	public sealed class ThinLensCamera
		: Camera
	{
		private readonly ISampler _lensSampler;
		private readonly double _viewPlaneDistance;
		private readonly double _lensRadius;
		private readonly double _zoomFactor;
		private readonly double _focalPlaneDistance;

		public ThinLensCamera( Point3 position, Point3 lookAt, Vector3 up, ISampler lensSampler, double lensRadius, double focalPlaneDistance, double viewPlaneDistance, double zoomFactor )
			: base( position, lookAt, up )
		{
			_lensSampler = lensSampler;
			_viewPlaneDistance = viewPlaneDistance;
			_lensRadius = lensRadius;
			_zoomFactor = zoomFactor;
			_focalPlaneDistance = focalPlaneDistance;
		}

		[Pure]
		public double ViewPlaneDistance
		{

			get { return _viewPlaneDistance; }
		}

		[Pure]
		public double ZoomFactor
		{

			get { return _zoomFactor; }
		}

		protected override IObservable<IRenderUpdate> RenderCore( Scene scene, ViewPlane viewPlane, ITracer tracer )
		{
			double size = 1d / _zoomFactor;
			double hWidth = viewPlane.PixelWidth / 2d;
			double hHeight = viewPlane.PixelHeight / 2d;

			var tasks = TaskService.Default.CreateCollection<IRenderUpdate>();

			for( int line = 0; line < viewPlane.PixelHeight; line++ )
			{
				double dLine = (double)line;
				var currentLine = line;

				tasks.Execute(
					() =>
					{
						for( int pixel = 0; pixel < viewPlane.PixelWidth; pixel++ )
						{
							double dPixel = (double)pixel;
							Color pixelColor = Color.Background;


							var lensSampler = new RandomSampler( viewPlane.Sampler.SampleCount ).GenerateSamplesDisc().GetEnumerator();
							foreach( Point2 sample in viewPlane.Sampler.GenerateSamplesSquare() )
							{
								Point2 pixelPoint =
									new Point2(
										size * ( dPixel - hWidth + sample.X ),
										size * ( dLine - hHeight + sample.Y )
										);

								lensSampler.MoveNext();
								Point2 dp = lensSampler.Current;
								Point2 lensPoint = dp * _lensRadius;

								Ray ray =
									new Ray(
										Position + lensPoint.X * U + lensPoint.Y * V,
										GetDirection( pixelPoint, lensPoint )
										);

								pixelColor += tracer.TraceRay( scene, ray );
							}
							pixelColor /= viewPlane.Sampler.SampleCount;

							//pixelColor *= exposure_time;
							viewPlane.Pixels[pixel, currentLine] = pixelColor;
						}

						return new LineRenderUpdate( viewPlane.PixelHeight - currentLine - 1, viewPlane.GetLine( currentLine ) );
					}
				);
			}

			return tasks.ObserveResults();
		}

		private Vector3 GetDirection( Point2 pixelPoint, Point2 lensPoint )
		{
			double x = pixelPoint.X * _focalPlaneDistance / _viewPlaneDistance;
			double y = pixelPoint.Y * _focalPlaneDistance / _viewPlaneDistance;

			return
				( ( x - lensPoint.X ) * U + ( y - lensPoint.Y ) * V - _focalPlaneDistance * W )
				.Normalize();
		}

	}
}